/*
 * =====================================================================================
 *
 *       Filename:  cl_timer_service.cxx
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  02/27/2011 06:49:09 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Ken Chen (Ken), chen_kenz@emc.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#ifndef __GXX_EXPERIMENTAL_CXX0X__
#define __GXX_EXPERIMENTAL_CXX0X__
#endif 

#include <sys/timerfd.h>
#include <unistd.h>
#include <cstring>
#include <algorithm>
#include "cl_timer_service.hxx"
#include "cl_timer.hxx"
#include "cl_mutex.hxx"
#include "cl_lock_guard.hxx"
#include "cl_channel.hxx" 
#include "cl_logger.hxx"

using namespace std;

namespace neo
{

CLTimerService::CLTimerService()
  : m_timerfd(::timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC)),
  m_lock(new CLMutex),
  m_timerChannel(new CLChannel(m_timerfd, CLChannel::CL_READ_EVENT)),
  m_timers(),
  m_cachedNextExpiredTime(CLTimestamp::nullTime())
{
	// Use real time type of timer.
	// Note, be careful if we need to change the system time.
	// It will impact the timers.
  if(m_timerfd < 0)
  {
	  char errmsg[512];
	  CLLOG_F("TIMER", "CLTimerService::ctor create timer failed %d %s",
				errno, strerror_r(errno, errmsg, sizeof(errmsg)));
  }
  else
  {
    m_timerChannel->setReadCallback(bind(&CLTimerService::exec, this));
  }
}

CLTimerService::~CLTimerService() 
{
  if(m_timerfd >= 0)
  {
    ::close(m_timerfd);
  }
}

CLId CLTimerService::schedule(const CLCallbackFunctor& timerCallback,
		                          const CLTimestamp& when,
															int msecsInterval)
{
  TimerSPtr aTimer(CLTimer::create(timerCallback, when, msecsInterval));
	addTimer(aTimer);
  return aTimer->identity();
}

void CLTimerService::addTimer(const TimerSPtr& aTimer) 
{
  bool earliestExpirationChanged = false;
  {
    CLLockGuard guard(*m_lock.get());
    earliestExpirationChanged = doAddTimerWithHoldLock(aTimer);
  }

  if(earliestExpirationChanged)
  {
    resetTimerfd(aTimer->expirationTime());
  }
}

void CLTimerService::removeTimer(const CLId& timerId) 
{
  bool earliestExpirationChanged = false;
  CLTimestamp when;
  {
    CLLockGuard guard(*m_lock.get());
    earliestExpirationChanged = doRemoveTimerWithHoldLock(timerId);
    if(earliestExpirationChanged)
    {
      if(!m_timers.empty())
      {
        when = (*m_timers.begin())->expirationTime();
      }
			else
			{
        earliestExpirationChanged = false;
			}
    }
  }

  if(earliestExpirationChanged)
  {
    resetTimerfd(when);
  }
}

CLChannel* CLTimerService::timerChannel(void) const
{
  return m_timerChannel.get();
}

void CLTimerService::exec(void) 
{
  CLTimestamp curTime = CLTimestamp::nowTime();
  TimerList expiredTimers;
  // Stage 1: hold the lock and find all expired timers
  {
    CLLockGuard guard(*m_lock.get());
    TimerList::iterator it = find_if(m_timers.begin(), 
				                             m_timers.end(), 
																		 TimerLaterThan(curTime));
    expiredTimers.splice(expiredTimers.begin(), 
				                 m_timers, 
												 m_timers.begin(), 
											   it);
  }

  // Stage 2: unlock the lock and execute the callback against the expired timers 
	// all callbacks should be quick, otherwise execute them will block for a while
	// and the next timer will be already due which impact the timer accuracy. 
  TimerList::iterator expiredIt = expiredTimers.begin();
  TimerList::iterator expiredEndIt = expiredTimers.end();
  for(; expiredIt != expiredEndIt; ++expiredIt)
  {
    (*expiredIt)->exec();
  }

  // Stage 3: hold the lock and push back the repeated timers
  CLTimestamp nextExpiredTime = CLTimestamp::nullTime();
  {
    CLLockGuard guard(*m_lock.get());
    expiredIt = expiredTimers.begin();
    expiredEndIt = expiredTimers.end();
    for(; expiredIt != expiredEndIt; ++expiredIt)
    {
      if((*expiredIt)->isRepeated()) 
      {
        (*expiredIt)->restart(curTime);
        doAddTimerWithHoldLock(*expiredIt);
      }
    }

    if(!m_timers.empty())
    {
      nextExpiredTime = (*m_timers.begin())->expirationTime();
    }
		m_cachedNextExpiredTime = nextExpiredTime;
  }

  // Stage 4: unlock the lock and reset timerfd coz the next expired time has changed
  resetTimerfd(nextExpiredTime);
}

bool CLTimerService::doAddTimerWithHoldLock(const TimerSPtr& aTimer) 
{
  bool earliestExpirationChanged = false;
  TimerList::iterator it = find_if(m_timers.begin(), 
			                             m_timers.end(), 
																	 TimerLaterThan(aTimer->expirationTime()));
  if(it == m_timers.begin())
  {
    earliestExpirationChanged = true; 
  }
  m_timers.insert(it, aTimer);

  return earliestExpirationChanged;
}

bool CLTimerService::doRemoveTimerWithHoldLock(const CLId& timerId) 
{
  bool earliestExpirationChanged = false;
  TimerList::iterator it = find_if(m_timers.begin(), m_timers.end(), TimerIdEqualTo(timerId));
  
  if(it != m_timers.end())
  {
    if(it == m_timers.begin())
    {
      earliestExpirationChanged = true; 
    }
    m_timers.erase(it);
  }
  else
  {
	  CLLOG_C("TIMER", "Timer Id %s is not registered with timer service", timerId.toString().c_str());
  }

  return earliestExpirationChanged;
}

void CLTimerService::resetTimerfd(const CLTimestamp& ts) 
{
#ifndef NDEBUG
	CLTimestamp curTime(CLTimestamp::nowTime());

	if((curTime > ts) || (curTime == ts))
	{
	  // "ts" should be in future compared to "curTime".
		// Otherwise the ts is already due.
    CLLOG_I("TIMER", "Timer %s is already due", ts.toFormatString().c_str());
	}
#endif

  int newSecs;
  int newMsecs;
	ts.getTime(newSecs, newMsecs);
	
  struct itimerspec newValue;
  ::bzero(&newValue, sizeof(newValue));
  newValue.it_value.tv_sec = newSecs; 
  newValue.it_value.tv_nsec = newMsecs*1000; 
  if(::timerfd_settime(m_timerfd, TFD_TIMER_ABSTIME, &newValue, NULL) < 0)
  {
	  char errmsg[512];
    CLLOG_C("TIMER", "CLTimerService::resetTimerfd: %d %s", 
				errno, strerror_r(errno, errmsg, sizeof(errmsg)));
  }
}

} // neo

